//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Diagnostics.Contracts;

namespace OpenLS.Spreadsheet.Expressions
{
    sealed partial class BinaryExpression : BinaryExpressionBase<Expression>
    {
        public override string ToString()
        {
            return string.Format("({0}){1}({2})", Left.ToString(), OperatorText, Right.ToString());
        }
        public BinaryExpression(Expression left, BinaryOperatorType op, Expression right)
            : base (left, right)
        {
            _operator = op;
        }
        internal override Expression Transposed(Cell target)
        {
            return new BinaryExpression(Left.Transposed(target), Operator, Right.Transposed(target));
        }
        protected override Expression CreateExpression(Expression left, Expression right)
        {
            return new BinaryExpression(left, _operator, right);
        }
        public override string GetText(TextContext context)
        {
            return Left.GetText(context) + OperatorText + Right.GetText(context); //\\todo: precedence
        }
        string OperatorText
        {
            get
            {
                switch (Operator)
                {
                    case BinaryOperatorType.Add: return "+";
                    case BinaryOperatorType.Equality: return "=";
                    case BinaryOperatorType.Inequality: return "<>";
                    case BinaryOperatorType.GreaterThan: return ">";
                    case BinaryOperatorType.GreaterThanOrEqual: return ">=";
                    case BinaryOperatorType.LessThan: return "<";
                    case BinaryOperatorType.LessThanOrEqual: return "<=";
                    case BinaryOperatorType.Subtract: return "-";
                    case BinaryOperatorType.Divide: return "/";
                    case BinaryOperatorType.Multiply: return "*";
                    case BinaryOperatorType.Power: return "^";
                    default: throw new NotSupportedException();
                }
            }
        }
        object getComparisonValue(object l, object r)
        {
            if (l is Error)
                return l;
            if (r is Error)
                return r;
            int res =  CellComparator.CompareValues(l, r);
            switch (Operator)
            {
                case BinaryOperatorType.Equality:
                    return res == 0;
                case BinaryOperatorType.LessThan:
                    return res < 0;
                case BinaryOperatorType.LessThanOrEqual:
                    return res <= 0;
                case BinaryOperatorType.GreaterThan:
                    return res > 0;
                case BinaryOperatorType.GreaterThanOrEqual:
                    return res >= 0;
                case BinaryOperatorType.Inequality:
                    return res != 0;
                default: throw new NotSupportedException();
            }
        }


        protected override object GetObjectImp(EvaluationContext context)
        {
            try
            {
                object l = Left.GetValue(context);
                object r = Right.GetValue(context);

                if (l is Complex && r is string)
                {
                    l = l.ToString();
                }
                if (r is Complex && l is string)
                {
                    r = r.ToString();
                }
                if (l is IArray && r is IArray)
                {
                    var leftArray = (IArray) l;
                    var rightArray = (IArray) r;
                    if (leftArray.RowCount == 1 && rightArray.ColumnCount == 1)
                    {
                        if (leftArray.ColumnCount == rightArray.RowCount)
                            return new LeftCarthesionBinaryExpressionArray(this, context, leftArray, rightArray);
                    }
                    if (leftArray.ColumnCount == 1 && rightArray.RowCount == 1)
                    {
                        if (leftArray.RowCount == rightArray.ColumnCount)
                            return new RightCarthesionBinaryExpressionArray(this, context, leftArray, rightArray);
                    }
                
                if (leftArray.RowCount != rightArray.RowCount)
                        return Errors.ValueError;
                    if (leftArray.ColumnCount != rightArray.ColumnCount)
                        return Errors.ValueError;

                    return new BinaryExpressionArray(this, context, leftArray, rightArray);
                }
                if (l is IArray)
                    return new LeftBinaryExpressionArray(this, context, (IArray) l, r);
                if (r is IArray)
                    return new RightBinaryExpressionArray(this, context,  l, (IArray)r);

                return computedValue(context, l, r);
            }
            catch (Exception)
            {
                return Errors.ValueError;
            }
        }

        private object computedValue(EvaluationContext context, object l, object r)
        {
            switch (Operator)
            {
                case BinaryOperatorType.Equality:
                case BinaryOperatorType.GreaterThan:
                case BinaryOperatorType.GreaterThanOrEqual:
                case BinaryOperatorType.Inequality:
                case BinaryOperatorType.LessThan:
                case BinaryOperatorType.LessThanOrEqual:
                    return getComparisonValue(l, r);
            }

            if (l is Error)
                return l;
            if (r is Error)
                return r;
            double leftValue;
            double rightValue;
            if (l is string)
            {
                if (!double.TryParse(l as string, out leftValue))
                {
                    DateTime leftDateValue;
                    if (!DateTime.TryParse(l as string, out leftDateValue))
                    {
                        return Errors.ValueError;
                    }
                    l = leftDateValue;
                }
            }
            if (r is string)
            {

                if (!double.TryParse(r as string, out rightValue))
                {
                    DateTime rightDateValue;
                    if (!DateTime.TryParse(r as string, out rightDateValue))
                    {
                        return Errors.ValueError;
                    }
                    r = rightDateValue;
                }
            }
            bool needsDate = false;
            if (l is DateTime)
            {
                l = context.Worksheet.Workbook.ChangeType<double>(l);
                needsDate = true;
            }
            if (r is DateTime)
            {
                r = context.Worksheet.Workbook.ChangeType<double>(r);
                needsDate = true;
            }
            if (l is Complex || r is Complex)
            {
                return compareComplex(l, r);
            }
            leftValue = Convert.ToDouble(l); //\\avoid exceptions
            rightValue = Convert.ToDouble(r);
            switch (Operator)
            {
                case BinaryOperatorType.Add:
                    return convertToDate(leftValue + rightValue, needsDate, context);
                case BinaryOperatorType.Subtract:
                    return convertToDate(leftValue - rightValue, needsDate, context);
                case BinaryOperatorType.Multiply:
                    return convertToDate( leftValue * rightValue, needsDate, context);
                case BinaryOperatorType.Divide:
                    try
                    {
                        if (rightValue == 0)
                            return Errors.DivError;
                        double result =  leftValue / rightValue;
                        return convertToDate( result, needsDate, context);
                    }
                    catch { return Errors.DivError; }
                case BinaryOperatorType.Power:
                    return Math.Pow(leftValue, rightValue);
                default: throw new NotSupportedException(OperatorText);
            }
        }

        private object compareComplex(object l, object r)
        {
            Complex left;
            Complex right;
            if (l is Complex)
                left = (Complex)l;
            else if (l is double)
                left = new Complex((double)l, 0);
            else
                throw new NotSupportedException();
            if (r is Complex)
                right= (Complex)r;
            else if (r is double)
                right = new Complex((double)r, 0);
            else
                throw new NotSupportedException();
            switch ( Operator)
            {
                case BinaryOperatorType.Add:
                    return left + right;
                case BinaryOperatorType.Divide:
                    return left / right;
                case BinaryOperatorType.Equality:
                    return left == right;
                case BinaryOperatorType.Inequality:
                    return left != right;
                case BinaryOperatorType.Multiply:
                    return left * right;
                case BinaryOperatorType.Subtract:
                    return left - right;
            }
            throw new NotSupportedException();
        }


        private static object convertToDate(double p, bool needsDate, EvaluationContext context)
        {
            if (needsDate)
                return context.Worksheet.Workbook.ChangeType<DateTime>(p);
            return p;
        }
        private readonly BinaryOperatorType _operator;
        public BinaryOperatorType Operator
        {
            get { return _operator; }
        }



    }
}